package Sintatico;

import Utils.PrintCollor;
import Utils.TipoProducao;
import Utils.Token;

import java.util.ArrayList;

import semantico.EnumTipo;
import semantico.Simbolo;
import semantico.TabelaSimbolos;
import static org.fusesource.jansi.Ansi.Color.*;

/**
 *
 * @author Adriano
 */
public class ParserRD {

    ArrayList<Token> tokens;
    int index;
    TabelaSimbolos tabelaSimbolos;
    private int escopo;
    private EnumTipo tipoAtual;

    public ParserRD(ArrayList<Token> tokens) {
        this.tokens = tokens;
        this.index = 0;
        this.tabelaSimbolos = new TabelaSimbolos();
        this.tipoAtual = EnumTipo.NULO;
    }

    public Token getToken() {
        if (hasProximo()) {
            PrintCollor.print(BLACK, "Index: " + this.index + " - "
                    + this.tokens.get(this.index));
            return this.tokens.get(this.index);
        } else {
            return new Token("FIM", "FIM", TipoProducao.FIM, 0, 0);
        }
    }

    public void moveProximo() {
        PrintCollor.print(GREEN, "Index: " + index + " PARA Index: "
                + (index + 1));
        this.index++;
    }

    public void moveAnterior() {
        PrintCollor.print(GREEN, "Index: " + index + " PARA Index: "
                + (index - 1));
        this.index--;
    }

    public boolean hasProximo() {
        return this.tokens.size() > this.index + 1;
    }

    private void printErro() {
        PrintCollor.print(RED, "ERRO " + this.index + " "
                + getToken().toString());
    }

    private void printErro(Token tokenSemantico) {
        PrintCollor.print(RED, "ERRO " + this.index + " "
                + tokenSemantico.toString());
    }

    public void analisar() {
        if (getToken().getTipo() == TipoProducao.PROGRAM) {
            this.tipoAtual = EnumTipo.PROGRAMA;
            program();
        } else {
            printErro();
        }
    }

    private void program() {
        PrintCollor.print(BLUE, "program");
        identList();
        moveProximo();
        if (getToken().getTipo() == TipoProducao.PONTO_VIRGULA) {
            moveProximo();
            if (getToken().getTipo() == TipoProducao.VAR) {
                defVar();
            } else {
                moveAnterior();
            }
            bloco();
        } else {
            printErro();
        }
    }

    private void identList() {
        PrintCollor.print(BLUE, "identList");
        moveProximo();
        if (getToken().getTipo() == TipoProducao.IDENTIFIER) {
            this.tabelaSimbolos.add(new Simbolo(getToken().getValor(), this.tipoAtual, getEscopo()));
            seqIdent();
        } else {
            moveAnterior();
        }

    }

    private EnumTipo getTokenTipo() {
        switch (getToken().getTipo()) {
            case INTEGER:
                return EnumTipo.INTEGER;
            case BOOLEAN:
                return EnumTipo.BOOLEAN;
            case REAL:
                return EnumTipo.REAL;
            case ALFA:
                return EnumTipo.ALFA;
            case CHAR:
                return EnumTipo.CHAR;
            default:
                return EnumTipo.IDENTE;
        }

    }

    private void bloco() {
        PrintCollor.print(BLUE, "bloco");
        moveProximo();
        novoEscopo();
        switch (getToken().getTipo()) {
            case CONSTANTE:
                defConst();
                break;
            case TYPE:
                defType();
                break;
            case VAR:
                defVar();
                defSubPrograma();
                break;
            case BEGIN:
                listaComandos();
                moveProximo();
                if (getToken().getTipo() == TipoProducao.END) {
                    PrintCollor.print(MAGENTA, "SUCESSO!");
                    PrintCollor.print(BLACK, "**** TABELA DE SÍMBOLOS ****");
                    System.out.println(this.tabelaSimbolos);
                } else {
                    printErro();
                }
                break;
            default:
                printErro();
        }
    }

    private void seqIdent() {
        PrintCollor.print(BLUE, "seqIdent");
        moveProximo();
        if (getToken().getTipo() == TipoProducao.VIRGULA) {
            moveProximo();
            if (getToken().getTipo() == TipoProducao.IDENTIFIER) {
                this.tabelaSimbolos.add(new Simbolo(getToken().getValor(), this.tipoAtual, getEscopo()));
                moveProximo();
                if (getToken().getTipo() == TipoProducao.VIRGULA) {
                    moveAnterior();
                    seqIdent();
                } else {
                    moveAnterior();
                }
            } else {
                printErro();
            }
        } else {
            moveAnterior();
        }

    }

    private void defConst() {
        PrintCollor.print(BLUE, "defConst");
        identList();
        moveProximo();
        if (getToken().getTipo() == TipoProducao.IGUAL) {
            moveProximo();
            if (getToken().getTipo() == TipoProducao.CONSTANTE) {
                moveProximo();
                if (getToken().getTipo() == TipoProducao.PONTO_VIRGULA) {
                    defConst();
                } else {
                    printErro();
                }
            } else {
                printErro();
            }
        } else {
            printErro();
        }
    }

    private void defType() {
        PrintCollor.print(BLUE, "defType");
        identList();
        moveProximo();
        if (getToken().getTipo() == TipoProducao.IGUAL) {
            type();
            moveProximo();
            if (getToken().getTipo() == TipoProducao.PONTO_VIRGULA) {
                defType();
            } else {
                printErro();
            }

        } else {
            printErro();
        }
    }

    private void defVar() {
        PrintCollor.print(BLUE, "defVar");
        this.tipoAtual = EnumTipo.NULO;
        identList();
        moveProximo();
        if (getToken().getTipo() == TipoProducao.DOIS_PONTOS) {
            type();
            moveProximo();
            if (getToken().getTipo() == TipoProducao.PONTO_VIRGULA) {
                defVar();
            } else {
                printErro();
            }

        } else {
            moveAnterior();
        }
    }

    private void defSubPrograma() {
        PrintCollor.print(BLUE, "defSubPrograma");
        moveProximo();
        if (getToken().getTipo() == TipoProducao.PROCEDURE) {
            moveProximo();
            if (getToken().getTipo() == TipoProducao.IDENTIFIER) {
                this.tabelaSimbolos.add(new Simbolo(getToken().getValor(), EnumTipo.METODO, getEscopo()));
                moveProximo();
                // tratar parametros
                if (getToken().getTipo() == TipoProducao.ABRE_PARENTESE) {
                    paramt();
                    moveProximo();
                    if (getToken().getTipo() == TipoProducao.FECHA_PARENTESE) {
                        moveProximo();
                        if (getToken().getTipo() == TipoProducao.PONTO_VIRGULA) {
                            bloco();
                        } else {
                            printErro();
                        }

                    } else {
                        printErro();
                    }
                } else {
                    printErro();
                }

            } else {
                printErro();
            }
        } else if (getToken().getTipo() == TipoProducao.FUNCTION) {
            moveProximo();
            if (getToken().getTipo() == TipoProducao.IDENTIFIER) {
                this.tabelaSimbolos.add(new Simbolo(getToken().getValor(), EnumTipo.METODO, getEscopo()));
                moveProximo();
                // tratar parametros
                if (getToken().getTipo() == TipoProducao.ABRE_PARENTESE) {
                    paramt();
                    moveProximo();
                    if (getToken().getTipo() == TipoProducao.FECHA_PARENTESE) {
                        moveProximo();
                        if (getToken().getTipo() == TipoProducao.DOIS_PONTOS) {
                            type();
                            moveProximo();
                            if (getToken().getTipo() == TipoProducao.PONTO_VIRGULA) {
                                bloco();
                            } else {
                                printErro();
                            }

                        } else {
                            printErro();
                        }

                    } else {
                        printErro();
                    }

                } else {
                    printErro();
                }
            } else {
                printErro();
            }
        } else {
            moveAnterior();
        }

    }

    private void listaComandos() {
        PrintCollor.print(BLUE, "defListaComandos");
        comando();
        seqComando();
    }

    private void comando() {
        PrintCollor.print(BLUE, "comando");
        moveProximo();
        switch (getToken().getTipo()) {
            case IDENTIFIER:
                Token tokenSemantico = getToken();
                moveProximo();
                if (getToken().getTipo() == TipoProducao.DOIS_PONTOS_IGUAL) {
                    if (!this.tabelaSimbolos.isValido(tokenSemantico, expressao())) {
                        printErro(tokenSemantico);
                    }
                }
                break;
            case BEGIN:
                listaComandos();
                moveProximo();
                if (getToken().getTipo() != TipoProducao.END) {
                    printErro();
                }
                break;
            case IF:
                expressao();
                moveProximo();
                if (getToken().getTipo() == TipoProducao.THEN) {
                    comando();
                    clausulaElse();
                } else {
                    printErro();
                }
                break;
            case REAPEAT:
                listaComandos();
                moveProximo();
                if (getToken().getTipo() == TipoProducao.UNTIL) {
                    expressao();
                } else {
                    printErro();
                }
                break;
            case WHILE:
                expressao();
                moveProximo();
                if (getToken().getTipo() == TipoProducao.DO) {
                    comando();
                } else {
                    printErro();
                }
                break;
            case FOR:
                moveProximo();
                if (getToken().getTipo() == TipoProducao.IDENTIFIER) {
                    if (this.tabelaSimbolos.possoUsar(getToken().getValor())) {
                        moveProximo();
                        if (getToken().getTipo() == TipoProducao.DOIS_PONTOS_IGUAL) {
                            expressao();
                            inc();
                            expressao();
                            moveProximo();
                            if (getToken().getTipo() == TipoProducao.DO) {
                                comando();
                            }
                        }
                    }
                } else {

                }
                break;
            default:
                moveAnterior();
                break;
        }
    }

    private void seqComando() {
        PrintCollor.print(BLUE, "seqComando");
        moveProximo();
        if (getToken().getTipo() == TipoProducao.PONTO_VIRGULA) {
            comando();
            seqComando();
        } else {
            moveAnterior();
        }
    }

    private ArrayList<Token> expressao() {
        PrintCollor.print(BLUE, "expressao");
        ArrayList<Token> token = new ArrayList<>();
        while (true) {
            token.add(getToken());
            moveProximo();
            if ((getToken().getTipo() == TipoProducao.PONTO_VIRGULA)
                    || (getToken().getTipo() == TipoProducao.DO)
                    || (getToken().getTipo() == TipoProducao.THEN)
                    || (getToken().getTipo() == TipoProducao.TO)) {
                break;
            }
        }
        moveAnterior();

        // Corrigir o express
        // AnalisadorSintatico express = new AnalisadorSintatico(this.tokens,
        // this.index);
        // express.analisar();
        return token;
    }

    private void clausulaElse() {
        PrintCollor.print(BLUE, "clausulaElse");
        moveProximo();
        if (getToken().getTipo() == TipoProducao.ELSE) {
            comando();
        } else {
            moveAnterior();
        }
    }

    private void inc() {
        PrintCollor.print(BLUE, "inc");
        moveProximo();
        switch (getToken().getTipo()) {
            case TO:
            case DOWNTO:
                break;
            default:
                printErro();
        }
    }

    private void paramt() {
        PrintCollor.print(BLUE, "paramt");
        defParamt();
        listParamt();
    }

    private void defParamt() {
        PrintCollor.print(BLUE, "defParamt");
        moveProximo();
        if (getToken().getTipo() == TipoProducao.VAR) {
            moveProximo();
        }
        identList();
        moveProximo();
        if (getToken().getTipo() == TipoProducao.DOIS_PONTOS) {
            type();
        } else {
            printErro();
        }
    }

    private void listParamt() {
        PrintCollor.print(BLUE, "defParamt");
        moveProximo();
        if (getToken().getTipo() == TipoProducao.PONTO_VIRGULA) {
            defParamt();
            listParamt();
        } else {
            moveAnterior();
        }
    }

    private void type() {
        PrintCollor.print(BLUE, "type");
        moveProximo();
        switch (getToken().getTipo()) {
            case INTEGER:
            case REAL:
            case CHAR:
            case BOOLEAN:
            case ALFA:
                this.tabelaSimbolos.setType(getTokenTipo());
                break;
            default:
                printErro();
        }
    }

    private void novoEscopo() {
        this.escopo++;
    }

    private int getEscopo() {
        return this.escopo;
    }
}
